/*

 * Copyright (c) 2009, Ramiro Espada and Jorge Hernandez
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
 
 package org.molamil.cascada.selectors {
	import flash.errors.IllegalOperationError;
	import flash.utils.getQualifiedClassName;
	import flash.events.EventDispatcher;
	
	/**
	 * Minimal <code>selector</code> containing a basic skeleton for implementation.
	 * 
	 * @author Jorge Hernandez
	 * @author Ramiro Espada
	 */
	public class AbstractSelector extends EventDispatcher {
		
		
		//TODO: Implement paging.
		
		
		// CONSTANTS
		
		protected const ERROR_INVALID_INPUT_TYPE:String = "ERROR: The input must be of type {requiredType}" +
														  " but was {currentType}";
		protected const ERROR_INVALID_CRITERIA_TYPE:String = "ERROR: The criteria must be of type {requiredType}" +
														     " but was {currentType}";
		private static const ERROR_NULL_INPUT:String = "ERROR: The input property must be not null";
		private static const ERROR_NULL_CRITERIA:String = "ERROR: The criteria must be not null";
		
		
		// PROPERTIES
	
		protected var inputClass:Class; 
		protected var criteriaClass:Class; 
		
		private var _input:Object; 
		private var _criteria: Object;

		
		// CONSTRUCTOR
		
		public function AbstractSelector() {
			
			// Abstract check.
			if (getQualifiedClassName(this) == "org.molamil.cascada.selectors::AbstractSelector")
            	throw new IllegalOperationError("AbstractSelector must be extended");
            	
		}
		
		
		// PUBLIC METHODS
		
		public function select():Array {
			
			// Pre-selection.
			onSelecting();
			
			checkInput();
			checkCriteria();
			
			// Selection business.
			var result:Array = doSelect();
			
			// Post-selection.
			onSelected();
			
			return result;
		}
		
		
		// PROTECTED METHODS
		
		protected function doSelect():Array {
			throw new IllegalOperationError("AbstractSelector.doSelect must be implemented");
			return null;
		}
		
		protected function checkInput():void {
			
			var errorMessage:String;
			
			if (input == null) {
				if (inputClass != null) {
					errorMessage = ERROR_NULL_INPUT;
					throw new SelectorError(errorMessage);
				} else if (!(input is inputClass)) {
					errorMessage = ERROR_INVALID_INPUT_TYPE;
					errorMessage = errorMessage.replace("{requiredType}", getQualifiedClassName(inputClass));
					errorMessage = errorMessage.replace("{currentType}", getQualifiedClassName(input));
					throw new SelectorError(errorMessage);
				}
			}
			
		}
		
		protected function checkCriteria():void {
			
			var errorMessage:String;
			
			if (criteria == null) {
				if (inputClass != null) {
					errorMessage = ERROR_NULL_CRITERIA;
					throw new SelectorError(errorMessage);
				} else if (!(criteria is criteriaClass)) {
					errorMessage = ERROR_INVALID_CRITERIA_TYPE;
					errorMessage = errorMessage.replace("{requiredType}", getQualifiedClassName(criteriaClass));
					errorMessage = errorMessage.replace("{currentType}", getQualifiedClassName(criteria));
					throw new SelectorError(errorMessage);
				}
			}
			
		}
	
		protected function onSelecting():void {
			dispatchEvent(new SelectorEvent(SelectorEvent.SELECTING));
		}
		
		protected function onSelected():void {
			dispatchEvent(new SelectorEvent(SelectorEvent.SELECTED));
		}
		
		
		// GETTER/SETTER
		
		public function get input():Object {
			return _input;
		}
		
		public function set input(input:Object):void {
			_input = input;
		}
		
		/**
		 * Criteria object, often a SelectorCriteria, containing information to create matching rules.
		 */
		public function get criteria():Object {
			return _criteria;
		}
		
		public function set criteria(criteria:Object):void {
			_criteria = criteria;
		}
	
	
	}
	
}
